/**********************************************************************
 * SailmiPIM - modular cross-platform feature Personal Information Manager Tools.
 * Copyright(C) 2006-2014  Georg Rudoy
 *
 * Distributed under the Boost Software License, Version 1.0.
 *(See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
 **********************************************************************/

#include "categoryselector.h"
#include <algorithm>
#include <QStringList>
#include <QCheckBox>
#include <QVariant>
#include <QVBoxLayout>
#include <QMoveEvent>
#include <QApplication>
#include <QStringListModel>
#include <QAction>
#include <QtDebug>
#include "ui_categoryselector.h"
#include "util.h"

namespace SailmiPIM::Util
{
	class CategorySelector::SelectorTagsModel : public QStringListModel
	{
		CategorySelector& Selector_;
		QSet<int> SelectedRows_;

		QString Header_;
	public:
		explicit SelectorTagsModel(CategorySelector& selector)
		: QStringListModel { &selector }
		, Selector_ { selector }
		{
		}

		QVariant headerData(int section, Qt::Orientation orientation, int role) const override
		{
			if(role != Qt::DisplayRole || orientation != Qt::Horizontal || section)
				return {};

			return Header_;
		}

		Qt::ItemFlags flags(const QModelIndex& index) const override
		{
			return(QStringListModel::flags(index) & ~Qt::ItemIsEditable) | Qt::ItemIsUserCheckable;
		}

		QVariant data(const QModelIndex& index, int role) const override
		{
			if(role == Qt::CheckStateRole)
				return SelectedRows_.contains(index.row()) ? Qt::Checked : Qt::Unchecked;

			return QStringListModel::data(index, role);
		}

		bool setData(const QModelIndex& index, const QVariant& value, int role) override
		{
			if(role != Qt::CheckStateRole)
				return false;

			if(value.value<Qt::CheckState>() == Qt::Checked)
				SelectedRows_ << index.row();
			else
				SelectedRows_.remove(index.row());
			emit dataChanged(index, index, { Qt::CheckStateRole });
			Selector_.NotifyTagsSelection();
			return true;
		}

		void SelectAll()
		{
			const int size = stringList().size();
			if(!size)
				return;

			SelectedRows_.reserve(size);
			for(int i = 0; i < size; ++i)
				SelectedRows_ << i;

			emit dataChanged(index(0), index(size - 1), { Qt::CheckStateRole });

			Selector_.NotifyTagsSelection();
		}

		void SelectNone()
		{
			const int size = stringList().size();
			if(!size)
				return;

			SelectedRows_.clear();
			emit dataChanged(index(0), index(size - 1), { Qt::CheckStateRole });

			Selector_.NotifyTagsSelection();
		}

		void SetHeader(QString header)
		{
			if(header == Header_)
				return;

			Header_ = std::move(header);
			emit headerDataChanged(Qt::Horizontal, 0, 0);
		}
	};

	CategorySelector::CategorySelector(QWidget *parent)
	: QDialog(parent)
	, Ui_ { new Ui::CategorySelector }
	, Model_ { *new SelectorTagsModel { *this } }
	, Separator_ { GetDefaultTagsSeparator() }
	{
		setWindowTitle(tr("Tags selector"));
		setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);

		Ui_->setupUi(this);
		Ui_->Tree_->setModel(&Model_);

        const auto& avail = QApplication::primaryScreen()->availableGeometry();
		setMinimumHeight(avail.height() / 3 * 2);

		const auto all = new QAction(tr("Select all"), this);
		all->setProperty("ActionIcon", "edit-select-all");
		connect(all,
				&QAction::triggered,
				this,
				&CategorySelector::SelectAll);

		const auto none = new QAction(tr("Select none"), this);
		none->setProperty("ActionIcon", "edit-select-none");
		connect(none,
				&QAction::triggered,
				this,
				&CategorySelector::SelectNone);

		Ui_->Tree_->addAction(all);
		Ui_->Tree_->addAction(none);

		Ui_->Tree_->setContextMenuPolicy(Qt::ActionsContextMenu);

		SetButtonsMode(parent ? ButtonsMode::NoButtons : ButtonsMode::Close);
	}

	void CategorySelector::SetCaption(const QString& caption)
	{
		Model_.SetHeader(caption);
	}

	void CategorySelector::SetPossibleSelections(QStringList tags, bool sort)
	{
		auto guard = DisableNotifications();

		if(sort)
			tags.sort();
		Model_.setStringList(tags);
		Model_.SelectNone();
	}

	QStringList CategorySelector::GetPossibleSelections() const
	{
		return Model_.stringList();
	}

	QStringList CategorySelector::GetSelections() const
	{
		const auto& allTags = Model_.stringList();
		const auto& selectedIdxes = GetSelectedIndexes();
		QStringList selected;
		selected.reserve(selectedIdxes.size());
		for(const auto idx : selectedIdxes)
			selected << allTags [idx];
		return selected;
	}

	QList<int> CategorySelector::GetSelectedIndexes() const
	{
		QList<int> result;

		const auto& rowCount = Model_.stringList().size();
		for(int i = 0; i < rowCount; ++i)
		{
			const auto state = Model_.index(i).data(Qt::CheckStateRole).value<Qt::CheckState>();
			if(state == Qt::Checked)
				result << i;
		}

		return result;
	}

	void CategorySelector::SetSelections(const QStringList& tags)
	{
		auto guard = DisableNotifications(false);

		const auto& allTags = Model_.stringList();
		const auto rowCount = allTags.size();
		for(int i = 0; i < rowCount; ++i)
		{
			const auto state = tags.contains(allTags [i]) ?
					Qt::Checked :
					Qt::Unchecked;
			Model_.setData(Model_.index(i), state, Qt::CheckStateRole);
		}
	}

	QString CategorySelector::GetSeparator() const
	{
		return Separator_;
	}

	void CategorySelector::SetSeparator(const QString& sep)
	{
		Separator_ = sep;
	}

	void CategorySelector::SetButtonsMode(ButtonsMode mode)
	{
		switch(mode)
		{
		case ButtonsMode::NoButtons:
			Ui_->ButtonsBox_->setVisible(false);
			break;
		case ButtonsMode::Close:
			Ui_->ButtonsBox_->setStandardButtons(QDialogButtonBox::Close);
			Ui_->ButtonsBox_->setVisible(true);
			break;
		case ButtonsMode::AcceptReject:
			Ui_->ButtonsBox_->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
			Ui_->ButtonsBox_->setVisible(true);
			break;
		}
	}

	void CategorySelector::moveEvent(QMoveEvent *e)
	{
		QWidget::moveEvent(e);
		QPoint pos = e->pos();
        QRect avail = QApplication::primaryScreen()->availableGeometry();
		int dx = 0, dy = 0;
		if(pos.x() + width() > avail.width())
			dx = width() + pos.x() - avail.width();
		if(pos.y() + height() > avail.height() &&
				height() < avail.height())
			dy = height() + pos.y() - avail.height();

		if(dx || dy)
			move(pos - QPoint(dx, dy));
	}

	void CategorySelector::SelectAll()
	{
		Model_.SelectAll();
	}

	void CategorySelector::SelectNone()
	{
		Model_.SelectNone();
	}

	void CategorySelector::SetSelectionsFromString(const QString& text)
	{
		auto guard = DisableNotifications(false);
		SetSelections(text.split(Separator_, Qt::SkipEmptyParts));
	}

	void CategorySelector::NotifyTagsSelection()
	{
		if(NotificationsEnabled_)
			emit tagsSelectionChanged(GetSelections());
	}

	DefaultScopeGuard CategorySelector::DisableNotifications(bool reemit)
	{
		auto prevValue = NotificationsEnabled_;
		NotificationsEnabled_ = false;
		return MakeScopeGuard([this, prevValue, reemit]
				{
					NotificationsEnabled_ = prevValue;
					if(reemit)
						NotifyTagsSelection();
				});
	}
}
